package com.download

import android.annotation.SuppressLint
import android.app.*
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.IBinder
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
import android.support.v4.content.FileProvider
import android.util.Log
import android.view.View
import android.widget.RemoteViews
import io.reactivex.Observable
import io.reactivex.Scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.*
import okio.*
import java.io.*
import java.util.*


class DownLoadService : Service(), ProgressListener {

    private val TAG = "DownLoadService"

    private val taskStack = Stack<String>()
    private lateinit var notification: Notification
    private lateinit var remoteViews: RemoteViews
    private var url: String? = null
    private var notificationId: Int = -1
    private var isRunning = false

    companion object {
        const val URL_KEY = "FILE_DownLoad_URL"
    }

    override fun onBind(intent: Intent?): IBinder? {
        Log.i(TAG, "onBind: ")
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent != null) {
            val downLoadUrl = intent.getStringExtra(URL_KEY)
            if (downLoadUrl != null) {
                addTask(downLoadUrl)
                if (!isRunning)
                    trigger()
            }
        }
        return super.onStartCommand(intent, flags, startId)
    }

    private fun trigger() {
        try {
            val url: String? = taskStack.pop()
            if (url == null)
                return
            if (isRunning) {
                return
            }
            this.url = url
            val client = OkHttpClient.Builder()
                    .addNetworkInterceptor({ chain ->
                        val response = chain.proceed(chain.request())
                        response
                                .newBuilder().body(ProgressResponseBody(response.body(), this@DownLoadService))
                                .build()
                    }).build()
            val request = Request.Builder()
                    .url(url)
                    .build()
            client.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call?, e: IOException) {
                    if (BuildConfig.DEBUG)
                        Log.i(TAG, "onFailure: ${e.message}")
                }

                @SuppressLint("LogNotTimber")
                override fun onResponse(call: Call?, response: Response?) {
                    if (response != null) {
                        createFile()
                        createNotification(url)
                        saveFileToLocalStorage(response)
                    }
                }

            })
        } catch (e: EmptyStackException) {
            if (BuildConfig.DEBUG)
                Log.i(TAG, "下载任务已悉数完成")
        }

    }

    private fun createFile() {
        val fileName = nameFromUrl()
        file = File(Environment.getExternalStorageDirectory(), fileName)
        if (file.exists().not())
            file.createNewFile()
    }

    lateinit var file: File
    private fun saveFileToLocalStorage(response: Response) {
        Observable.create<DownLoadInfo> {
            try {
                val bufFos = BufferedOutputStream(FileOutputStream(file))
                val contentLen = response.body()!!.contentLength()
                var byteRead = 0L
                val downLoadInfo = DownLoadInfo(contentLen, byteRead, false)
                val stream = response.body()!!.byteStream()
                val buff = ByteArray(4096)
                var len = -1
                isRunning = true
                while (stream.read(buff).also { len = it } != -1) {
                    bufFos.write(buff, 0, len)
                    bufFos.flush()
                    byteRead += len
                    downLoadInfo.byteRead = byteRead
                    downLoadInfo.done = byteRead == -1L
                    it.onNext(downLoadInfo)
                }
                downLoadInfo.byteRead = -1
                downLoadInfo.done = true
                it.onNext(downLoadInfo)
                it.onComplete()
                stream.close()
                bufFos.close()
                isRunning = false
            } catch (e: Exception) {
                //下载过程中出了问题
                e.printStackTrace()
            }

        }.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    onProgressUpdate(it.totalByte, it.byteRead, it.byteRead == -1L)
                }, {

                }, {

                })
    }

    private fun nameFromUrl(): String {
        var fileName: String
        if (url!!.endsWith(".apk"))
            fileName = url!!.substring(url!!.lastIndexOf('/'), url!!.lastIndexOf('.'))
        else fileName = url!!.substring(url!!.lastIndexOf('/'))
        if (fileName.length > 20)
            fileName = fileName.substring(0, 11)

        return fileName.plus(".apk")
    }


    private lateinit var notificationManagerCompat: NotificationManagerCompat


    private fun createNotification(url: String) {
        notificationManagerCompat = NotificationManagerCompat.from(this)
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val pendingIntent = createApkInstallIntent()
        if (Build.VERSION.SDK_INT >= 26) {
            val notificationChannel = NotificationChannel(BuildConfig.APPLICATION_ID, "DownLoadUi", NotificationManager.IMPORTANCE_LOW)
            notificationChannel.enableLights(true)
            notificationChannel.setShowBadge(false)
//            notificationChannel.setSound(null,null)
            notificationManager.createNotificationChannel(notificationChannel)
            remoteViews = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.download_notification_layout)
            notification = NotificationCompat
                    .Builder(this, BuildConfig.APPLICATION_ID)
                    .setTicker("趣味星球")
                    .setSmallIcon(R.drawable.star)
                    .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.star_big))
                    .setContent(remoteViews)
                    .setContentIntent(pendingIntent)
                    .build()
            notification.contentIntent = pendingIntent
            notificationId += 1
            notificationManagerCompat.notify(notificationId, notification)
        } else {

            remoteViews = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.download_notification_layout)
            notification = NotificationCompat
                    .Builder(this, BuildConfig.APPLICATION_ID)
                    .setTicker("趣味星球")
                    .setSmallIcon(R.drawable.star)
                    .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.star_big))
                    .setContent(remoteViews)
                    .setContentIntent(pendingIntent)
                    .build()
            notification.contentIntent = pendingIntent
            notificationId += 1
            notificationManagerCompat.notify(notificationId, notification)
        }
    }

    private fun createApkInstallIntent(): PendingIntent {
        val intent = Intent()
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            intent.action = Intent.ACTION_INSTALL_PACKAGE
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        } else {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            intent.setDataAndType(FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file),
                    "application/vnd.android.package-archive")
        }
        intent.`package` = "com.android.packageinstaller"
        return PendingIntent.getActivity(
                this,
                1,
                intent,
                FLAG_UPDATE_CURRENT)

    }

    override fun onProgressUpdate(totalBytes: Long, bytesRead: Long, done: Boolean) {
        if (done) {
            isRunning = false
        }
        notifyNotification(totalBytes, bytesRead, done)
    }

    private fun notifyNotification(totalBytes: Long, bytesRead: Long, done: Boolean) {
        val progress = (bytesRead * 100f / totalBytes).toInt()
        if (done) {
            remoteViews.setViewVisibility(R.id.downloading_state_layout, View.GONE)
            remoteViews.setViewVisibility(R.id.download_complete_layout, View.VISIBLE)
        } else {
            remoteViews.setProgressBar(R.id.progressBar, 100, progress, false)
        }
        notificationManagerCompat.notify(notificationId, notification)

        if (done)
            trigger()
    }

    private fun addTask(downLoadUrl: String) = taskStack.add(downLoadUrl)

    class ProgressResponseBody(var responseBody: ResponseBody?, var listener: ProgressListener) : ResponseBody() {

        var bufferSource: BufferedSource? = null

        override fun contentLength() = responseBody!!.contentLength()


        override fun contentType() = responseBody!!.contentType()


        override fun source(): BufferedSource {
            if (bufferSource == null) {
                bufferSource = Okio.buffer(source(responseBody!!.source()))
            }
            return bufferSource!!
        }

        private fun source(source: Source): Source {
            return object : ForwardingSource(source) {
                var totalBytesRead = 0L
                override fun read(sink: Buffer?, byteCount: Long): Long {
                    val bytesRead = super.read(sink!!, byteCount)
                    // read() returns the number of bytes read, or -1 if this source is exhausted.
                    totalBytesRead += if (bytesRead != -1L) bytesRead else 0
//                    listener.onProgressUpdate(responseBody!!.contentLength(), totalBytesRead, bytesRead == -1L)
                    return bytesRead
                }
            }
        }

    }

}